/*
    快捷菜单类
    调用 showWin 方法可以显示。
*/

#Include lzShowTip.ahk

CapsLock & 2::{
    keywait("CapsLock")
    lzMenu2.showWin() 
}
CapsLock & 3::{
    keywait("CapsLock")
    lzMenu3.showWin() 
}

; 预生成快捷窗体，用时弹出会快。就是缓存的意思。
lzMenu2 := LZMenu("lzLib\LZMenu2.ini")
lzMenu3 := LZMenu("lzLib\LZMenu3.ini")

class LZMenu
{
    ;
    ;生成、显示和隐藏快捷键提示窗口
    ;
    __New(iniFile) {
        If not FileExist(iniFile)
        {
            Msgbox "快捷键盘的配置文件 %iniFile% 无效。"
            Return
        }
        this._wsr := envGet("wsr") . "\" ; 相对路径的根
        this._keyList := ""
        this._isClear := 0  ; 用来切换删除/正常状态
        this._iniFile := iniFile
        SplitPath iniFile,,,, &_iniFileName  ; 用来给 gui 命名。
        this._guiName := _iniFileName . "999999999"
        this._gui := this._genWin(this._guiName)  ; 以此为名生成窗口
        this._tipWinTitle := "lzCustomTipNotSameAsOthers"    ; 固定窗口标题
        ; 设置窗口显示出来时的热键，不显示时则无效。
        hotIfWinActive this._guiName
        Hotkey("LButton",  (*)=>this._lzClick(), "S")
        Hotkey("RButton",  (*)=>this._lzClick(), "S")
        Hotkey("MButton",  (*)=>this._lzClick(), "S")
        hotIfWinActive
        ; 处理其他按键
        this._ih := this._genIh()
    }

    _genIh() {
        ih := InputHook("E")
        ih.keyOpt("{ALL}", "SE")
        return ih
    }

    _genWin(aGuiName) {
        ; 生成便捷窗口;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
        theGui := Gui("+AlwaysOnTop -Caption +ToolWindow +LastFound",aGuiName)
        theGui.backcolor := "333333"
        theGui.setFont("c00FFFF")
        this._makeKeyLine(theGui)
        return theGui
    }

    _makeKeyLine(aGui) {
        theWidth := a_screenwidth / 15
        iconLoc := theWidth / 2 - 16
        this._keyList := iniRead(this._iniFile)
        Loop parse this._keyList, "`n", "`r"
        {
            ; 读出文件中 show 和 iconFile 键的值
            show := iniRead(this._iniFile, A_LoopField, "show", "")
            iconFile := iniRead(this._iniFile, A_LoopField, "iconFile", "")
            ; 如果不为空，是相对路径就转换为绝对路径，绝对路径不变。
            if (iconFile) {
                iconFile := SubStr(iconFile, 2, 1) != ":" ? this._wsr . iconFile : iconFile
            }
            controlNumber := A_Index * 4 - 3
            ; 以q,a,z为每行开头。
            if inStr("qaz", A_LoopField) {
                ; 加控件边框，这个字母不显示，但点击时运行的程序需要它
                aGui.add("Text", "0x12 section xm w" . theWidth . " h" . theWidth, A_LoopField)
            }
            else {
                aGui.add("Text", "0x12 section ys0 w" . theWidth . " h" . theWidth, A_LoopField)
            }
            aGui.add("Text", "xs9 ys9 cAAAAAA BackgroundTrans", A_LoopField)
            try
                aGui.add("Picture", "xs+" . iconLoc . " ys+" . iconLoc . " icon", iconFile)
            catch
                aGui.add("Picture", "xs+" . iconLoc . " ys+" . iconLoc . " icon", "")
            aGui.add("Text", "BackgroundTrans cyellow xs yp+38 center w" . theWidth, show)
        }
        aGui.add("Text", "c999999 x10", "注：红色背景为清除模式。按 Delete 键"
                . "切换清除模式与普通模式。鼠标左键运行右键重设中键删除。")
    }

    showWin() {
        ; 首先恢复为正常模式
        if this._isClear {
            this._isClear := 0
            this._gui.backColor := "333333"
        }
        this._gui.show()
        Suspend True
        loop {
            this._ih.start()
            this._ih.wait()
            if not this._ih.endKey {
                this._gui.hide()
                Suspend False
                return
            }
            if (this._ih.endKey == "Delete") {
                this._toClear()
                continue
            } else {
                break
            }
        }
        Suspend False
        this._gui.hide()
        if inStr(this._keyList, this._ih.endKey) {
            this._runApp(strUpper(this._ih.endKey))
        }
    }

    _runApp(key) {
        ;
        ; 此方法用来选择清除、设置或者运行
        ;
        if (this._isClear) {    ; 删除状态下就清理掉
            this._clearKey(key)
            return
        }
        appName := iniRead(this._iniFile, key, "appName", "")
        if (appName = "") {
            this._setKey(key)
            Return
        }
        ; 相对路径要转成绝对路径。
        appName := SubStr(appName, 2, 1) != ":" ? this._wsr . appName : appName
        args := iniRead(this._iniFile, key, "args", "")
        show := iniRead(this._iniFile, key, "show", "")
        ; 使用相对路径时候不能设置工作目录，否则有些程序会启动不了。
        ; 解决办法是使用绝对路径。
        ; run appName . args, RegExReplace(appName,  "[^\\]*$")
        try {
            lzShowTip("启动 " . show)
            run(appName . " " . args, RegExReplace(appName,  "[^\\]*$"))
        } catch {
            msgbox "此应用未能正常启动！`n`n" . appName
        }
    }

    _toClear() {
        if (this._isClear == 0) {
            lzShowTip("进入删除模式！",, 0xFF0000)
            this._isClear := 1
            this._gui.backColor := "red"
        } else {
            lzShowTip("恢复正常模式！",, 0x0000FF)
            this._isClear := 0
            this._gui.backColor := "333333"
        }
    }

    _clearKey(key) {
        refused := MsgBox("确定要删除快捷键 " . key . " 吗？",, "4") == "No"
         if (refused) {
             return
         }
        iniWrite("", this._iniFile, key,)    ; 清除配置文件中的相关内容
        this._setControl(key,"", "")
    }

    _setKey(key) {
        refused := MsgBox("现在要设置快捷键 " . key . " 吗？",, "4") == "No"
        if refused {
            return
        }
        appName := fileSelect(,, "只应选择可执行文件",)
        if (appName == "") {
            Return
        }
        iconFile := fileSelect(,, "请选择图标文件，不选择则使用可执行文件",)
        if (iconFile == "") {
            iconFile := appName
        }
        args := InputBox("请输入程序需要的参数", "输入参数").value
        show := InputBox("输入将要显示的程序的名称，简短些！", "输入程序名称").value
        if (show == "") ; 没有输入就使用程序名
            SplitPath(appName,,,, &show)
        iniwrite(appName, this._iniFile, key, "appName")
        iniwrite(iconFile, this._iniFile, key, "iconFile")
        iniwrite(args, this._iniFile, key, "args")
        iniwrite(show, this._iniFile, key, "show")
        this._setControl(key, iconFile, show)
    }

    _lzClick() {
        winID := winGetID(this._guiName)
        mouseGetPos(,, &winID1, &theControl)
        this._ih.stop()   ; 关闭 inputhook 否则一直接管键盘输入
        this._gui.hide()
        if (winID != winID1 or theControl == "") { ; 不在此窗口上的点击直接关闭窗口
            return
        }
        controlNumber := "Static" . (SubStr(theControl, 7) - 1) // 4 * 4 + 1
        key := controlGetText(controlNumber)
        if (A_ThisHotkey = "LButton")
            this._runApp(key)
        else if (A_ThisHotkey = "RButton")
            this._setKey(key)
        else if (A_ThisHotkey = "MButton")
            this._clearKey(key)
    }

    _setControl(key, value1, value2) {
        ;
        ; 根据 key 设置控件的值
        ;
        ; 首先根据 keylist 推算出控件的 ClassNN
        baseNumber := (InStr(this._keyList, key) - 1) * 2
        iconClassNN := "Static" . (baseNumber + 3)
        showClassNN := "Static" . (baseNumber + 4)
        this._gui[iconClassNN].value := value1
        this._gui[showClassNN].value := value2
    }

}
